/* ==============================================================================
*
*   Copyright (c) 2010, Pierre Favier
*   All rights reserved.
*
*   NTasking is free software: you can redistribute it and/or modify it
*   under the terms of the GNU General Public License as published by the
*   Free Software Foundation.
*
*   Redistribution and use in source and binary forms, with or without
*   modification, are permitted provided that the following conditions
*   are met:
*
*   -   Redistributions of source code must retain the above copyright notice,
*       this list of conditions and the following disclaimer.
*
*   -   Redistributions in binary form must reproduce the above
*       copyright notice, this list of conditions and the following
*       disclaimer in the documentation and/or other materials
*       provided with the distribution.
*
*   THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
*   WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
*   OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
*   DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
*   BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
*   EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
*   TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
*   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
*   ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
*   TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
*   THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
*   SUCH DAMAGE.
*
* ============================================================================== */

using System;
using System.Threading;
using NTasking.ValidationTests.ACATS_3_0.Support;
using NUnit.Framework;
using Rhino.Mocks;

namespace NTasking.ValidationTests.ACATS_3_0.C9
{

    [TestFixture(Description = "")]
    public class C940013 : ValidationTest
    {

        //      Use a small subset of the freeway ramp simulation shown in other
        //      tests.  With the timing pulse off (which prevents items from being
        //      removed from the queue) queue up a small number of calls.  Start the
        //      timing pulse and, at the first execution of the entry code, check the
        //      'count attribute. Empty the queue.   Pass the items being removed from
        //      the queue to the Ramp_Sensor_01 task; there check that the items are
        //      arriving in FIFO order.  Check the final 'count value  
        //      
        //      Send another batch of items at a rate which will, if the delay timing
        //      of the implementation is reasonable, cause the queue length to
        //      fluctuate in both directions.   Again check that all items arrive
        //      FIFO.  At the end check that the 'count returned to zero reflecting
        //      the empty queue.  


        private const int ClearLevel = 0;
        private const int MinimumLevel = 1;
        private const int ModerateLevel = 2;
        private const int SeriousLevel = 4;
        private const int CriticalLevel = 6;

        private const int tcExpectedPassageTotal = 624;

        // For this test give each vehicle an integer ID incremented
        // by one for each successive vehicle.  In reality this would be
        // a more complex alpha-numeric ID assigned at pickup time.
        private static int nextId = 0;

        // In reality this would be about 5 seconds. The default value of 
        // this constant in the implementation defined package is similar
        // but could, of course be considerably different - it would not 
        // affect the test 
        private const double pulseTimeDelta = Impdef.ClearReadyQueue;

        private static bool tcFailed1 = false;

        private static Ramp testRamp;
        private static PulseState pulseState;
        private static RampSensor rampSensor01;


        private class Ramp : Protected
        {

            private bool releaseOneVehicle = false;
            private bool meterInUse = true;

            private bool tcWaitAtMeterFirst = true;
            private int tcEntryQueueCount = 0;
            private int tcPassageTotal = 0;
            private int tcPassPointWAM = 23;


            [Entry]
            internal Action WaitAtMeter { get; private set; }


            [ReadWriteOperation]
            internal Action TimePulseReceived { get; private set; }

            [ReadWriteOperation]
            internal Action<int> TcPassage { get; private set; }


            [ReadOnlyOperation]
            internal Func<bool> MeterInUseState { get; private set; }

            [ReadOnlyOperation]
            internal Func<int> TcGetPassageTotal { get; private set; }

            [ReadOnlyOperation]
            internal Func<int> TcGetCount { get; private set; }


            public Ramp()
            {
                // External call for Meter_in_Use
                ReadOnlyOperation(MeterInUseState, Is: () =>
                {
                    return meterInUse;
                });


                // Trace the paths through the various routines by totaling the
                // weighted call parameters
                ReadWriteOperation(TcPassage, Is: passPoint =>
                {
                    tcPassageTotal += passPoint;
                });


                // For the final check of the whole test
                ReadOnlyOperation(TcGetPassageTotal, Is: () =>
                {
                    return tcPassageTotal;
                });


                ReadOnlyOperation(TcGetCount, Is: () =>
                {
                    return tcEntryQueueCount;
                });


                // Here each Vehicle task queues itself awaiting release
                Entry(WaitAtMeter,
                    When: () => releaseOneVehicle,
                Is: () =>
                // EXAMPLE OF ENTRY WITH BARRIERS AND PERSISTENT SIGNAL
                {
                    TcPassage(tcPassPointWAM); // note passage through here

                    // For this test three vehicles are queued before the first
                    // is released.  If the queueing mechanism is working correctly
                    // the first time we pass through here the entry'count should
                    // reflect this

                    if (tcWaitAtMeterFirst)
                        if (Count(WaitAtMeter) != 2)
                            tcFailed1 = true;

                    tcWaitAtMeterFirst = false;

                    tcEntryQueueCount = Count(WaitAtMeter); // note for later

                    releaseOneVehicle = false; // Consume the signal
                    ; // stub ::: Decrement count of number of vehicles on ramp 
                });


                ReadWriteOperation(TimePulseReceived, Is: () =>
                {
                    int Load = MinimumLevel;
                    bool FreewayBreakdown = false;

                    // if broken down, no vehicles are released
                    if (!FreewayBreakdown)
                    {
                        if (Load < ModerateLevel)
                            releaseOneVehicle = true;

                        // stub  ::: If other levels, release every other pulse, every third pulse  etc.
                    }
                });
            }

        }


        private class PulseState : Protected
        {

            private bool state = false;


            [ReadWriteOperation]
            internal Action StartPulse { get; private set; }

            [ReadWriteOperation]
            internal Action StopPulse { get; private set; }


            [ReadOnlyOperation]
            internal Func<bool> Pulsing { get; private set; }


            public PulseState()
            {
                ReadWriteOperation(StartPulse, Is: () =>
                {
                    state = true;
                });


                ReadWriteOperation(StopPulse, Is: () =>
                {
                    state = false;
                });


                ReadOnlyOperation(Pulsing, Is: () =>
                {
                    return state;
                });
            }

        }


        private class PulseTask : ValidationTestTask
        {

            internal PulseTask(IValidationTestResult result)
                : base(result)
            {
            }


            protected override void Body()
            {
                DateTime pulseTime;

                while (!pulseState.Pulsing())
                {
                    // Starts up in the quiescent state
                    Ada.Delay(Impdef.MinimumTaskSwitch);
                }
                pulseTime = DateTime.Now;
                while (pulseState.Pulsing())
                {
                    Ada.Delay(pulseTime);
                    testRamp.TimePulseReceived(); // Transmit pulse to test_ramp
                    // ::::::::::  and to all the other ramps
                    pulseTime += TimeSpan.FromSeconds(pulseTimeDelta); // calculate next
                }
            }
            protected override void Exception(Exception ex)
            {
                Console.WriteLine("Unexpected exception in Pulse_Task");
                Result.TestFailed();
            }
        }


        private class RampSensor : ValidationTestTask
        {
            private const int tcPassPoint = 31;


            [Entry]
            internal Action<int> AcceptVehicle { get; private set; }

            [Entry]
            internal Action TcFirstThreeHandled { get; private set; }

            [Entry]
            internal Action TcAllDone { get; private set; }


            internal RampSensor(IValidationTestResult result)
                : base(result)
            {
            }


            protected override void Body()
            {
                int thisId = 0;
                int tcLastId = 1;

                while (true)
                {
                    Select(s => s.

                        Accept(AcceptVehicle, id =>
                        {
                            ;   // stub:::: match up with next Real-Time notification
                            // from the sensor.  Requeue to next ramp sensor
                            thisId = id;

                            // The following is all Test_Control code
                            testRamp.TcPassage(tcPassPoint); // note passage

                            // The items arrive in the order they are taken from
                            // the Wait_at_Meter entry queue
                            if (thisId - tcLastId != 1)
                            {
                                // The tasks are being queued (or unqueued) in the
                                // wrong order
                                Console.WriteLine("Queueing on the Wait_at_Meter queue failed");
                                Result.TestFailed();
                            }

                            tcLastId = thisId; // for the next check

                            if (tcLastId == 4)
                                // rendezvous with the test driver
                                Accept(TcFirstThreeHandled);
                            else if (tcLastId == 9)
                                // rendezvous with the test driver
                                Accept(TcAllDone);
                        }).

                    Or.

                        Terminate

                    );
                }
            }
            protected override void Exception(Exception ex)
            {
                Console.WriteLine("Unexpected exception in RampSensor Task");
                Console.WriteLine(ex);
                Result.TestFailed();
            }
        }


        // Carrier task. One is created for each vehicle arriving at the ramp
        private class Vehicle : ValidationTestTask
        {
            private const int tcPassPoint = 1;
            private const int tcPassPoint2 = 21;
            private const int tcPassPoint3 = 2;


            [Entry]
            internal Action<int> GetId { get; private set; }


            internal Vehicle(IValidationTestResult result, int number)
                : base(result)
            {
                Name = "Vehicle#" + number;
            }


            protected override void Body()
            {

                try
                {
                    int thisId = 0;

                    Accept(GetId, id =>
                    {
                        thisId = id;
                    });

                    if (testRamp.MeterInUseState())
                    {
                        testRamp.TcPassage(tcPassPoint2); // note passage
                        ;  // stub::: Increment count of number of vehicles on ramp 
                        testRamp.WaitAtMeter(); // Queue on the meter entry
                    }

                    // Call to the first in the series of the Ramp_Sensors
                    //     this "passes" the vehicle from one sensor to the next
                    //     Each sensor will requeue the call to the next thus this
                    //     rendezvous will only be completed as the vehicle is released
                    //     by the last sensor on the ramp.

                    rampSensor01.AcceptVehicle(thisId);
                }
                catch (Exception ex)
                {
                    Console.WriteLine("Unexpected exception in Vehicle Task\n" + ex);
                    Result.TestFailed();
                }
            }
        }


        // Simulate the arrival of a vehicle at the Ramp_Receiver and the
        // generation of an accompanying carrier task
        private static void NewArrival(IValidationTestResult result)
        {
            const int tcPassPoint = 3;

            Vehicle vehicle = new Vehicle(result, nextId + 1);
            vehicle.Activate();
            //vehicle.Start();

            nextId += 1;

            vehicle.GetId(nextId);

            testRamp.TcPassage(tcPassPoint); // Note passage through here

            Ada.Delay(Impdef.MinimumTaskSwitch);
        }


        [Test(Description =
                "Check that items queued on a protected entry are handled FIFO and that" +
                "the  'count attribute of that entry reflects the length of the queue.")]
        public void Test()
        {
            IValidationTestResult result = Mocks.DynamicMock<IValidationTestResult>();

            nextId = 1;
            tcFailed1 = false;

            Ada.Declare(() =>
            {
                testRamp = new Ramp();
                pulseState = new PulseState();

                new PulseTask(result);
                rampSensor01 = new RampSensor(result);
                // Setup text expectations

                Expect.Call(result.TestFailed).Repeat.Never();
                Mocks.ReplayAll();
            },
            Begin: () =>
            {
                // Start test execution

                try
                {
                    // Test driver.  This is ALL test control code

                    // Arrange to queue three vehicles on the Wait_at_Meter queue.  The 
                    // timing pulse is quiescent so the queue will build
                    for (int i = 1; i <= 3; i++)
                        NewArrival(result);

                    Ada.Delay(pulseTimeDelta); // ensure all is settled

                    // Start the timing pulse, the queue will be serviced
                    pulseState.StartPulse();

                    // wait here until the first three are complete
                    rampSensor01.TcFirstThreeHandled();

                    if (testRamp.TcGetCount() != 0)
                    {
                        Console.WriteLine("Intermediate Wait_at_Entry'count is incorrect");
                        result.TestFailed();
                    }

                    // generate new arrivals at a rate that will make the queue increase
                    // and decrease "randomly"
                    for (int i = 1; i <= 5; i++)
                    {
                        NewArrival(result);
                        Ada.Delay(pulseTimeDelta / 2);
                    }

                    // wait here until all have been handled
                    rampSensor01.TcAllDone();

                    if (testRamp.TcGetCount() != 0)
                    {
                        Console.WriteLine("Final Wait_at_Entry'count is incorrect");
                        result.TestFailed();
                    }

                    pulseState.StopPulse(); // finish test

                    if (tcExpectedPassageTotal != testRamp.TcGetPassageTotal())
                    {
                        Console.WriteLine("Unexpected paths taken");
                        result.TestFailed();
                    }

                    if (tcFailed1)
                    {
                        Console.WriteLine("Wait_at_Meter'count incorrect");
                        result.TestFailed();
                    }
                }
                catch (Exception ex)
                {
                    Console.WriteLine("Unexpected exception in _main_");
                    Console.WriteLine(ex);
                    result.TestFailed();
                }
            });
        }

    }

}
